<template>
    <BForm :id="id" class="tnxbsv-form" :class="{initializing: initializing, 'form-inline': inline}">
        <slot></slot>
    </BForm>
</template>

<script>
import {BForm} from 'bootstrap-vue-next';

export default {
    name: 'TnxbsvForm',
    components: {BForm},
    props: {
        id: {
            type: String,
            default: () => 'tnxbsv-form-' + new Date().getTime(),
        },
        model: {
            type: Object,
            default: () => ({}),
        },
        inline: Boolean,
        rules: {
            type: Object, // key: 字段名，value: 验证规则数组
            default: () => ({}),
        },
        disabled: Boolean,
    },
    data() {
        return {
            initializing: true,
            fieldEventListeners: {},
        };
    },
    watch: {
        rules() {
            this.initRules();
        },
        disabled() {
            this.updateElementsDisabled();
        },
    },
    mounted() {
        window.addEventListener('resize', this.updateLabelWidth);
        this.$nextTick(() => {
            setTimeout(() => {
                this.initRules();
                this.updateLabelWidth();
                this.updateElementsDisabled();
                this.initializing = false;
            });
        });
    },
    beforeUnmount() {
        window.removeEventListener('resize', this.updateLabelWidth);
        this.clearEventListeners();
    },
    methods: {
        getFieldGroupElements() {
            let elements = {};
            let groups = this.$el.querySelectorAll('.b-form-group[prop]');
            groups.forEach(group => {
                let fieldName = group.getAttribute('prop');
                elements[fieldName] = group;
            });
            return elements;
        },
        getFieldRules(fieldName) {
            let fieldRules = this.rules[fieldName] || [];
            if (!Array.isArray(fieldRules)) {
                fieldRules = [fieldRules];
            }
            return fieldRules;
        },
        addFieldEventListener(fieldName, element, eventType, handler) {
            if (!this.fieldEventListeners[fieldName]) {
                this.fieldEventListeners[fieldName] = {};
            }
            if (!this.fieldEventListeners[fieldName][eventType]) {
                this.fieldEventListeners[fieldName][eventType] = [];
            }
            element.addEventListener(eventType, handler);
            this.fieldEventListeners[fieldName][eventType].push(handler);
        },
        clearEventListeners() {
            for (let fieldName in this.fieldEventListeners) {
                let listeners = this.fieldEventListeners[fieldName];
                for (let eventType in listeners) {
                    let handlers = listeners[eventType];
                    let fieldElement = this.getFieldElement(fieldName);
                    if (fieldElement) {
                        handlers.forEach(handler => {
                            fieldElement.removeEventListener(eventType, handler);
                        });
                    }
                }
            }
            this.fieldEventListeners = {}; // 清空映射表
        },
        initRules() {
            this.clearEventListeners();

            let fieldGroupElements = this.getFieldGroupElements();
            for (let fieldName in fieldGroupElements) {
                let fieldGroupElement = fieldGroupElements[fieldName];
                if (fieldGroupElement) {
                    let fieldRules = this.getFieldRules(fieldName);
                    if (fieldRules.some(rule => rule.required)) {
                        let label = fieldGroupElement.querySelector('.form-label');
                        if (label) {
                            label.classList.add('is-required');
                        }
                    }
                    let existsChangeValidator = fieldRules.some(fieldRule => fieldRule.trigger === 'change');
                    let existsBlurValidator = fieldRules.some(fieldRule => fieldRule.trigger !== 'change');
                    if (existsChangeValidator || existsBlurValidator) {
                        let fieldElement = fieldGroupElement.querySelector('input, select, textarea');
                        if (fieldElement) {
                            if (existsChangeValidator) {
                                this.addFieldEventListener(fieldName, fieldElement, 'change', () => {
                                    this.validateField(fieldName, 'change');
                                });
                            }
                            if (existsBlurValidator) {
                                this.addFieldEventListener(fieldName, fieldElement, 'blur', () => {
                                    this.validateField(fieldName, 'blur');
                                });
                            }
                        }
                    }
                }
            }
        },
        updateLabelWidth() {
            if (this.inline) {
                return;
            }
            let maxWidth = 0;
            let groupElements = this.$el.querySelectorAll('.b-form-group');
            for (let groupElement of groupElements) {
                let label = groupElement.querySelector('.form-label');
                if (label) {
                    let width = label.offsetWidth;
                    if (width > maxWidth) {
                        maxWidth = width;
                    }
                }
            }
            if (maxWidth) {
                this.$el.style.setProperty('--label-width', `${maxWidth}px`);
            }
        },
        validate() {
            this.clearValidate(); // 清除之前的反馈信息

            return new Promise((resolve, reject) => {
                let errors = {};
                let fieldGroupElements = this.getFieldGroupElements();
                for (let fieldName in fieldGroupElements) {
                    let fieldErrorMessages = this.validateField(fieldName);
                    if (fieldErrorMessages.length) {
                        errors[fieldName] = fieldErrorMessages;
                    }
                }
                if (Object.keys(errors).length) {
                    reject(errors);
                }
                resolve();
            });
        },
        getFieldGroupElement(fieldName) {
            return this.$el.querySelector(`.b-form-group[prop="${fieldName}"]`);
        },
        queryFieldElement(fieldGroupElement) {
            return fieldGroupElement ? fieldGroupElement.querySelector('input, textarea, select') : null;
        },
        getFieldElement(fieldName) {
            let fieldGroupElement = this.getFieldGroupElement(fieldName);
            return this.queryFieldElement(fieldGroupElement);
        },
        validateField(fieldName, trigger) {
            const fieldValue = this.model[fieldName];
            const fieldRules = this.getFieldRules(fieldName);
            const fieldErrorMessages = [];

            fieldRules.forEach(fieldRule => {
                if (typeof fieldRule.validator === 'function' && (!trigger || trigger === fieldRule.trigger)) {
                    fieldRule.validator(fieldRule, fieldValue, (error) => {
                        if (error) {
                            fieldErrorMessages.push(error.message);
                        }
                    });
                }
                // 没有validator()方法，则不进行校验
            });

            if (fieldErrorMessages.length) {
                this.setFieldInvalidFeedback(fieldName, fieldErrorMessages);
            } else {
                // 清除错误状态
                this.removeFieldInvalidFeedback(fieldName);
            }
            return fieldErrorMessages;
        },
        setFieldInvalidFeedback(fieldName, messages) {
            let fieldGroupElement = this.getFieldGroupElement(fieldName);
            if (fieldGroupElement) {
                let feedbackDiv = fieldGroupElement.querySelector('.invalid-feedback');
                if (!feedbackDiv) {
                    feedbackDiv = document.createElement('div');
                    feedbackDiv.className = 'invalid-feedback';
                    let contentWrapper = fieldGroupElement.querySelector('.tnxbsv-form-group__content-wrapper');
                    // 有分组元素的情况下，一定有contentWrapper
                    contentWrapper.appendChild(feedbackDiv);
                }
                // 更新 .invalid-feedback 的内容
                feedbackDiv.textContent = messages.join('; ');
                // 设置输入框为无效状态
                let fieldElement = this.queryFieldElement(fieldGroupElement);
                if (fieldElement) {
                    // 如果字段元素或其上级元素中不包含.ignore-input-feedback样式，则加入.is-invalid样式
                    if (!fieldElement.classList.contains('ignore-input-feedback')
                        && !fieldElement.closest('.ignore-input-feedback')) {
                        fieldElement.classList.add('is-invalid');
                    }
                }
            }
        },
        removeFieldInvalidFeedback(fieldName) {
            let fieldGroupElement = this.getFieldGroupElement(fieldName);
            if (fieldGroupElement) {
                let feedbackDiv = fieldGroupElement.querySelector('.invalid-feedback');
                if (feedbackDiv) {
                    feedbackDiv.remove();
                }
                // 移除无效状态
                let fieldElement = this.queryFieldElement(fieldGroupElement);
                if (fieldElement) {
                    fieldElement.classList.remove('is-invalid');
                }
            }
        },
        clearValidate() {
            const invalidElements = this.$el.querySelectorAll('.is-invalid');
            invalidElements.forEach(el => el.classList.remove('is-invalid'));

            const invalidFeedbackElements = this.$el.querySelectorAll('.invalid-feedback');
            invalidFeedbackElements.forEach(el => el.remove());
        },
        scrollToField(fieldName) {
            let fieldGroupElement = this.getFieldGroupElement(fieldName);
            if (fieldGroupElement) {
                let fieldElement = this.queryFieldElement(fieldGroupElement);
                if (fieldElement) {
                    fieldElement.scrollIntoView({behavior: 'smooth', block: 'center'});
                } else {
                    fieldGroupElement.scrollIntoView({behavior: 'smooth', block: 'center'});
                }
            }
        },
        updateElementsDisabled() {
            let elements = this.$el.querySelectorAll('input, textarea, select, button');
            elements.forEach(element => {
                element.disabled = this.disabled;
            });
        },
    }
}
</script>

<style>
.tnxbsv-form.initializing .form-label {
    visibility: hidden;
}

.tnxbsv-form .b-form-group {
    display: flex;
}

.tnxbsv-form .b-form-group:not(:last-child) {
    margin-bottom: 1rem;
}

.tnxbsv-form .b-form-group .form-label {
    white-space: nowrap;
    min-width: var(--label-width);
    text-align: right;
    margin-right: 0.75rem;
    margin-bottom: 0;
}

.tnxbsv-form .b-form-group .form-label.is-required::before {
    content: '*';
    color: var(--bs-danger);
    margin-right: 0.25rem;
}

.tnxbsv-form .b-form-group .invalid-feedback {
    display: block;
}

.tnxbsv-form .inline-feedback .invalid-feedback {
    margin-top: 0;
    margin-left: 0.5rem;
    width: auto;
    flex-grow: 1;
}

@media (max-width: var(--bs-breakpoint-md)) {
    .tnxbsv-form .b-form-group {
        flex-direction: column;
    }

    .tnxbsv-form .b-form-group .form-label {
        text-align: left;
        margin-bottom: 0.5rem;
        min-width: auto;
    }
}
</style>
